package com.futuremind.recyclerviewfastscroll;

import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.DragInfo;
import ohos.agp.components.ListContainer;
import ohos.agp.components.Text;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Canvas;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.TextTool;
import ohos.app.Context;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import com.futuremind.recyclerviewfastscroll.viewprovider.DefaultScrolledViewProvider;
import com.futuremind.recyclerviewfastscroll.viewprovider.ScrollerViewProvider;

public class FastScroller extends DirectionalLayout {
    private static final int STYLE_NONE = -1;

    private final ListContainerScrolledListener scrollListener = new ListContainerScrolledListener(this);
    private ListContainer listContainer;

    private Component bubble;
    private Component handle;
    private Text bubbleTextView;
    private float bubbleOffset;
    private Color handleColor;
    private Color bubbleColor;
    private Color bubbleTextColor;
    private int bubbleTextSize;
    private String textStyle;
    private int scrolledOrientation;
    private int totalScrollH = 0;
    private int totalScrollW = 0;
    private int maxVisibility;
    private boolean manuallyChangingPosition;
    private ScrollerViewProvider viewProvider;
    private SectionTitleProvider titleProvider;
    private float offset = 0;

    public FastScroller(Context context) {
        super(context);
        setClipEnabled(false);
        initArrange();
    }

    public FastScroller(Context context, AttrSet attrSet) {
        super(context, attrSet);
        setClipEnabled(false);
        initAttrSet(attrSet);
        initArrange();
    }

    public FastScroller(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        setClipEnabled(false);
        initAttrSet(attrSet);
        initArrange();
    }

    /**
     * 初始化属性值
     *
     * @param attrSet
     */
    private void initAttrSet(AttrSet attrSet) {
        handleColor = AttrValue.get(attrSet, "handleColor", new Color(STYLE_NONE));
        bubbleColor = AttrValue.get(attrSet, "bubbleColor", new Color(STYLE_NONE));
        bubbleTextColor = AttrValue.get(attrSet, "bubbleTextColor", new Color(STYLE_NONE));
        bubbleTextSize = AttrValue.getDimension(attrSet, "bubbleTextSize", STYLE_NONE);
        textStyle = AttrValue.get(attrSet, "textStyle", "");
        maxVisibility = getVisibility();
        setOrientation(getOrientation());
        setViewProvider(new DefaultScrolledViewProvider());
    }

    /**
     * Enables custom layout for {@link FastScroller}.
     *
     * @param viewProvider A {@link ScrollerViewProvider} for the {@link FastScroller} to use when building layout.
     */
    public void setViewProvider(ScrollerViewProvider viewProvider) {
        removeAllComponents();
        this.viewProvider = viewProvider;
        viewProvider.setFastScroller(this);
        bubble = viewProvider.provideBubbleView(this);
        handle = viewProvider.provideHandleView(this);
        bubbleTextView = viewProvider.provideBubbleTextView();
        addComponent(bubble);
        addComponent(handle);
        initDrag();
    }

    /**
     * 设置拖拽监听
     */
    private void initDrag() {
        handle.setDraggedListener(Component.DRAG_HORIZONTAL_VERTICAL, new DraggedListener() {
            @Override
            public void onDragDown(Component component, DragInfo dragInfo) {
            }

            @Override
            public void onDragStart(Component component, DragInfo dragInfo) {
            }

            @Override
            public void onDragUpdate(Component component, DragInfo dragInfo) {
            }

            @Override
            public void onDragEnd(Component component, DragInfo dragInfo) {
            }

            @Override
            public void onDragCancel(Component component, DragInfo dragInfo) {
            }

            @Override
            public boolean onDragPreAccept(Component component, int dragDirection) {
                return true;
            }
        });
    }

    /**
     * Attach the {@link FastScroller} to {@link ListContainer}. Should be used after the adapter is set
     * to the {@link ListContainer}. If the adapter implements SectionTitleProvider, the FastScroller
     * will show a bubble with title
     *
     * @param listContainer A {@link ListContainer} to attach the {@link FastScroller} to.
     */
    public void setListContainer(ListContainer listContainer) {
        this.listContainer = listContainer;
        if (listContainer.getItemProvider() instanceof SectionTitleProvider) {
            titleProvider = (SectionTitleProvider) listContainer.getItemProvider();
        }
        listContainer.setScrolledListener(scrollListener);
        invalidateVisibility();
        listContainer.setLayoutRefreshedListener(new LayoutRefreshedListener() {
            @Override
            public void onRefreshed(Component component) {
                Component cp = listContainer.getComponentAt(0);
                if (cp != null && totalScrollH == 0) {
                    int height = cp.getHeight();
                    int width = cp.getWidth();
                    totalScrollH = height * listContainer.getItemProvider().getCount();
                    totalScrollW = width * listContainer.getItemProvider().getCount();
                }
            }
        });

        listContainer.addItemVisibilityChangedListener(new ListContainer.ItemVisibilityChangedListener() {
            @Override
            public void onItemAdded(Component component, int i) {
                invalidateVisibility();
            }

            @Override
            public void onItemRemoved(Component component, int i) {
                invalidateVisibility();
            }
        });
    }

    /**
     * 设置监听
     */
    private void initArrange() {
        addDrawTask(new DrawTask() {
            @Override
            public void onDraw(Component component, Canvas canvas) {
                bubbleOffset = viewProvider.getBubbleOffset();
                applyStyling();
                initHandleMovement();
            }
        });

        setLayoutRefreshedListener(new LayoutRefreshedListener() {
            @Override
            public void onRefreshed(Component component) {
                initHandleMovement();
                invalidateVisibility();
                bubbleOffset = viewProvider.getBubbleOffset();
            }
        });
    }

    /**
     * 获取可滑动的总高度
     *
     * @return 高度总滑动值
     */
    protected int getTotalScrollH() {
        return totalScrollH;
    }

    /**
     * 获取可滑动总宽度
     *
     * @return 宽度总滑动值
     */
    protected int getTotalScrollW() {
        return totalScrollW;
    }

    private void initHandleMovement() {
        handle.setTouchEventListener(new TouchEventListener() {
            @Override
            public boolean onTouchEvent(Component component, TouchEvent event) {
                MmiPoint mScreenPosition = event.getPointerScreenPosition(0);

                if (event.getAction() == TouchEvent.PRIMARY_POINT_DOWN || event.getAction() == TouchEvent.POINT_MOVE) {
                    if (titleProvider != null && event.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
                        viewProvider.onHandleGrabbed();
                        float offsetX = mScreenPosition.getX() - handle.getLocationOnScreen()[0];
                        float offsetY = mScreenPosition.getY() - handle.getLocationOnScreen()[1];
                        offset = isVertical() ? offsetY : offsetX;
                        return true;
                    }
                    manuallyChangingPosition = true;
                    float relativePos = getRelativeTouchPosition(event);
                    if (relativePos > 1) relativePos = 1;
                    else if (relativePos < 0) relativePos = 0;
                    setScrolledPosition(relativePos);
                    setRecyclerViewPosition(relativePos);
                    return true;
                } else if (event.getAction() == TouchEvent.PRIMARY_POINT_UP) {
                    manuallyChangingPosition = false;
                    if (titleProvider != null) {
                        viewProvider.onHandleReleased();
                    }
                }
                return true;
            }
        });
    }

    private float getRelativeTouchPosition(TouchEvent event) {
        if (isVertical()) {
            float yInParent = event.getPointerScreenPosition(0).getY() - Utils.getViewRawY(handle) - offset;
            return yInParent / (getHeight() - handle.getHeight());
        } else {
            float xInParent = event.getPointerScreenPosition(0).getX() - Utils.getViewRawX(handle) - offset;
            return xInParent / (getWidth() - handle.getWidth());
        }
    }

    private void setRecyclerViewPosition(float relativePos) {
        if (listContainer == null) return;
        int itemCount = listContainer.getItemProvider().getCount();
        int targetPos = (int) Utils.getValueInRange(0, itemCount - 1, (int) (relativePos * (float) itemCount));
        if (isVertical()) {
            listContainer.scrollTo(0, (int) (relativePos * getTotalScrollH()));
        } else {
            listContainer.scrollTo((int) (relativePos * getTotalScrollW()), 0);
        }
        if (titleProvider != null && bubbleTextView != null)
            bubbleTextView.setText(titleProvider.getSectionTitle(targetPos));
        scrollListener.notifyListeners(relativePos);
    }

    void setScrolledPosition(float relativePos) {
        if (isVertical()) {
            bubble.setTranslationY(Utils.getValueInRange(
                0,
                getHeight() - bubble.getHeight(),
                relativePos * (getHeight() - handle.getHeight()) + bubbleOffset) - bubble.getTop()
            );
            handle.setTranslationY(Utils.getValueInRange(
                0,
                getHeight() - handle.getHeight(),
                relativePos * (getHeight() - handle.getHeight())) - handle.getTop()
            );
        } else {
            bubble.setTranslationX(Utils.getValueInRange(
                0,
                getWidth() - bubble.getWidth(),
                relativePos * (getWidth() - handle.getWidth()) + bubbleOffset) - bubble.getLeft()
            );
            handle.setTranslationX(Utils.getValueInRange(
                0,
                getWidth() - handle.getWidth(),
                relativePos * (getWidth() - handle.getWidth())) - bubble.getLeft()
            );
        }
    }

    private void invalidateVisibility() {
        if (listContainer.getItemProvider() == null
            || listContainer.getItemProvider().getCount() == 0
            || isRecyclerViewNotScrollable()
            || maxVisibility != Component.VISIBLE
        ) {
            super.setVisibility(INVISIBLE);
        } else {
            super.setVisibility(VISIBLE);
        }
    }

    private boolean isRecyclerViewNotScrollable() {
        if (isVertical()) {
            return totalScrollH <= listContainer.getHeight();
        } else {
            return totalScrollW <= listContainer.getWidth();
        }
    }

    private void applyStyling() {
        if (bubbleColor.getValue() != STYLE_NONE) setBackgroundTint(bubbleTextView, bubbleColor);
        if (handleColor.getValue() != STYLE_NONE) setBackgroundTint(handle, handleColor);
        if (bubbleTextColor.getValue() != STYLE_NONE) bubbleTextView.setTextColor(bubbleTextColor);
        if (bubbleTextSize != STYLE_NONE) {
            bubbleTextView.setTextSize(bubbleTextSize);
        }
        if (!TextTool.isNullOrEmpty(textStyle)) {
            Font font = null;
            if (TextTool.isEqual("bold", textStyle)) {
                font = new Font.Builder("").setWeight(Font.BOLD).build();
            } else if (TextTool.isEqual("italic", textStyle)) {
                font = new Font.Builder("").makeItalic(true).build();
            }
            if (font != null) {
                bubbleTextView.setFont(font);
            }
        }
    }

    private void setBackgroundTint(Component view, Color color) {
        final ShapeElement background = view.getBackgroundElement()
            instanceof ShapeElement ? (ShapeElement) view.getBackgroundElement() : null;
        if (background == null) return;
        background.setRgbColor(RgbColor.fromArgbInt(color.getValue()));
        Utils.setBackground(view, background);
    }

    @Override
    public void setVisibility(int visibility) {
        maxVisibility = visibility;
        invalidateVisibility();
    }

    /**
     * Set the orientation of the {@link FastScroller}. The orientation of the {@link FastScroller}
     * should generally match the orientation of connected  {@link ListContainer} for good UX but it's not enforced.
     * Note: This method is overridden from {@link DirectionalLayout#setOrientation(int)} but for {@link FastScroller}
     * it has a totally different meaning.
     *
     * @param orientation of the {@link FastScroller}. {@link #VERTICAL} or {@link #HORIZONTAL}
     */
    @Override
    public void setOrientation(int orientation) {
        scrolledOrientation = orientation;
        super.setOrientation(orientation == HORIZONTAL ? VERTICAL : HORIZONTAL);
    }

    /**
     * Set the background color of the bubble.
     *
     * @param color Color in hex notation with alpha channel, e.g. 0xFFFFFFFF
     */
    public void setBubbleColor(Color color) {
        bubbleColor = color;
        invalidate();
    }

    /**
     * Set the background color of the handle.
     *
     * @param color Color in hex notation with alpha channel, e.g. 0xFFFFFFFF
     */
    public void setHandleColor(Color color) {
        handleColor = color;
        invalidate();
    }

    /**
     * Sets the text appearance of the bubble.
     *
     * @param bubbleTextColor The id of the resource to be used as text Color of the bubble.
     */
    public void setBubbleTextColor(Color bubbleTextColor) {
        this.bubbleTextColor = bubbleTextColor;
        invalidate();
    }

    /**
     * 设置文字样式
     *
     * @param tStyle 样式
     */
    public void setTextStyle(String tStyle) {
        this.textStyle = tStyle;
        invalidate();
    }

    /**
     * Sets the text appearance of the bubble.
     *
     * @param bubbleTextSize The id of the resource to be used as text TextSize of the bubble.
     */
    public void setBubbleTextSize(int bubbleTextSize) {
        this.bubbleTextSize = bubbleTextSize;
        invalidate();
    }

    /**
     * 添加监听
     * Add a {@link ListContainerScrolledListener}
     *
     * @param listener
     */
    public void addScrolledListener(ListContainerScrolledListener.ScrolledListener listener) {
        scrollListener.addScrolledListener(listener);
    }

    public boolean isVertical() {
        return scrolledOrientation == VERTICAL;
    }

    boolean shouldUpdateHandlePosition() {
        return handle != null && !manuallyChangingPosition && listContainer.getChildCount() > 0;
    }

    ScrollerViewProvider getViewProvider() {
        return viewProvider;
    }
}
